﻿#if UNITY_EDITOR
using Net.Helper;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

[Serializable]
public class SharePathData
{
    public string path;
    public string extraPath;

    public SharePathData()
    {
    }

    public SharePathData(string path)
    {
        this.path = path;
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        if (obj is SharePathData data)
            return data.path == path;
        return false;
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

[Serializable]
public class ProjectPathData : SharePathData
{
    public List<SharePathData> sharePaths = new();
    public List<string> filters = new();

    public ProjectPathData()
    {
    }

    public ProjectPathData(string path) : base(path)
    {
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        if (obj is ProjectPathData data)
            return data.path == path;
        return false;
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

[Serializable]
public class ExternalReferencesConfig
{
    public List<ProjectPathData> ProjectPaths = new();
    public int Mode;
    public string SearchPattern = "*.cs|*.txt|*.ini|*.conf|*.xls|*.xlsx|*.json|*.bytes";
}

public class ExternalReferenceObject : ScriptableObject
{
    public ExternalReferencesConfig Config;
}

public class ExternalReferenceTools : EditorWindow
{
    private ExternalReferenceObject externalReferenceObject;
    private ExternalReferencesConfig Config => externalReferenceObject.Config;
    private SerializedObject serializedObject;
    private ReorderableList _reorderableList;
    private Dictionary<int, ReorderableList[]> subLists = new();
    private Vector2 scrollPosition;

    [MenuItem("GameDesigner/Tools/ExternalReference")]
    static void ShowWindow()
    {
        var window = GetWindow<ExternalReferenceTools>();
        window.titleContent = new GUIContent("双端代码共享工具", EditorGUIUtility.IconContent("d_FolderFavorite Icon").image);
        window.Show();
    }

    private void OnEnable()
    {
        LoadData();
    }

    private void OnDisable()
    {
        SaveData();
    }

    private void LoadData()
    {
        externalReferenceObject = CreateInstance<ExternalReferenceObject>();
        externalReferenceObject.Config = PersistHelper.Deserialize<ExternalReferencesConfig>("external References.json");
        serializedObject = new SerializedObject(externalReferenceObject);
        var projectPropertys = serializedObject.FindProperty("Config").FindPropertyRelative("ProjectPaths");
        _reorderableList = new ReorderableList(serializedObject, projectPropertys, true, true, true, true);
        _reorderableList.drawElementCallback = OnDrawElementCallback;
        _reorderableList.drawHeaderCallback = DrawHeaderCallback;
        _reorderableList.elementHeightCallback = DrawHeightCallback;
    }

    private float DrawHeightCallback(int index)
    {
        float height = 0f;
        if (subLists.TryGetValue(index, out var list))
            height = list[0].GetHeight() + list[1].GetHeight();
        return (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 2 + height;
    }

    private void DrawHeaderCallback(Rect rect)
    {
        EditorGUI.LabelField(rect, "项目共享列表:");
    }

    private void OnDrawElementCallback(Rect rect, int index, bool isActive, bool isFocused)
    {
        var projectProperty = _reorderableList.serializedProperty.GetArrayElementAtIndex(index);
        rect.height = (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
        var pathProperty = projectProperty.FindPropertyRelative("path");
        pathProperty.stringValue = EditorGUI.TextField(new Rect(rect.position, new Vector2(rect.width - 35f, rect.height)), new GUIContent("项目路径:"), pathProperty.stringValue, EditorStyles.helpBox);
        if (GUI.Button(new Rect(new Vector2(rect.width - 10, rect.y), new Vector2(30f, rect.height)), EditorGUIUtility.IconContent("d_FolderOpened Icon")))
        {
            var csprojPath = EditorUtility.OpenFilePanel("选择项目文件", "", "csproj");
            if (!string.IsNullOrEmpty(csprojPath))
            {
                csprojPath = PathHelper.GetRelativePath(Application.dataPath, csprojPath);
                Config.ProjectPaths[index].path = csprojPath;
            }
        }

        rect.y += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
        var extrProperty = projectProperty.FindPropertyRelative("extraPath");
        extrProperty.stringValue = EditorGUI.TextField(new Rect(rect.position, new Vector2(rect.width - 35f, rect.height)), new GUIContent("扩展路径:"), extrProperty.stringValue, EditorStyles.helpBox);
        rect.y += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
        if (!subLists.TryGetValue(index, out var lists))
        {
            lists = new ReorderableList[2];
            var sharePaths = projectProperty.FindPropertyRelative("sharePaths");
            lists[0] = new ReorderableList(sharePaths.serializedObject, sharePaths, true, true, true, true);
            lists[0].drawHeaderCallback = (Rect rect) => EditorGUI.LabelField(rect, "共享代码列表:");
            lists[0].elementHeightCallback = (int index) => (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 2f;
            lists[0].drawElementCallback = (Rect subRect, int subIndex, bool subActive, bool subFocused) =>
            {
                var sharePathProperty = sharePaths.GetArrayElementAtIndex(subIndex);
                subRect.height = (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
                var path1Property = sharePathProperty.FindPropertyRelative("path");
                path1Property.stringValue = EditorGUI.TextField(new Rect(subRect.position, new Vector2(subRect.width - 35f, subRect.height)), new GUIContent("共享路径:"), path1Property.stringValue, EditorStyles.helpBox);
                if (GUI.Button(new Rect(new Vector2(subRect.width + 10f, subRect.y), new Vector2(30f, subRect.height)), EditorGUIUtility.IconContent("d_FolderOpened Icon")))
                {
                    var sharePath = EditorUtility.OpenFolderPanel("选择共享文件夹", "", "");
                    if (!string.IsNullOrEmpty(sharePath))
                    {
                        sharePath = PathHelper.GetRelativePath(Application.dataPath, sharePath);
                        Config.ProjectPaths[index].sharePaths[subIndex].path = sharePath;
                    }
                }

                subRect.y += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
                var extr1Property = sharePathProperty.FindPropertyRelative("extraPath");
                extr1Property.stringValue = EditorGUI.TextField(new Rect(subRect.position, new Vector2(subRect.width - 35f, subRect.height)), new GUIContent("扩展路径:"), extr1Property.stringValue, EditorStyles.helpBox);

                if (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform)
                {
                    if (subRect.Contains(Event.current.mousePosition))
                    {
                        DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
                        foreach (var obj in DragAndDrop.objectReferences)
                        {
                            Config.ProjectPaths[index].sharePaths[subIndex].path = AssetDatabase.GetAssetPath(obj);
                        }
                    }
                }
            };
            var filterPropertys = projectProperty.FindPropertyRelative("filters");
            lists[1] = new ReorderableList(sharePaths.serializedObject, filterPropertys, true, true, true, true);
            lists[1].drawHeaderCallback = (Rect rect) => EditorGUI.LabelField(rect, "路径过滤器:");
            lists[1].drawElementCallback = (Rect subRect, int subIndex, bool subActive, bool subFocused) =>
            {
                var filterProperty = filterPropertys.GetArrayElementAtIndex(subIndex);
                filterProperty.stringValue = EditorGUI.TextField(new Rect(subRect.position, new Vector2(subRect.width - 70f, subRect.height)), new GUIContent("过滤路径:"), filterProperty.stringValue, EditorStyles.helpBox);
                if (GUI.Button(new Rect(new Vector2(subRect.width + 10, subRect.y), new Vector2(30f, subRect.height)), EditorGUIUtility.IconContent("d_FolderOpened Icon")))
                {
                    var sharePath = EditorUtility.OpenFilePanelWithFilters("选择过滤", "", new string[] { });
                    if (!string.IsNullOrEmpty(sharePath))
                    {
                        sharePath = PathHelper.GetRelativePath(Application.dataPath, sharePath);
                        Config.ProjectPaths[index].filters[subIndex] = sharePath;
                    }
                }

                if (GUI.Button(new Rect(new Vector2(subRect.width - 20, subRect.y), new Vector2(30f, subRect.height)), EditorGUIUtility.IconContent("d_FolderFavorite Icon")))
                {
                    var sharePath = EditorUtility.OpenFolderPanel("选择过滤", "", "");
                    if (!string.IsNullOrEmpty(sharePath))
                    {
                        sharePath = PathHelper.GetRelativePath(Application.dataPath, sharePath);
                        Config.ProjectPaths[index].filters[subIndex] = sharePath;
                    }
                }

                if (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform)
                {
                    if (subRect.Contains(Event.current.mousePosition))
                    {
                        DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
                        foreach (var obj in DragAndDrop.objectReferences)
                        {
                            Config.ProjectPaths[index].filters[subIndex] = AssetDatabase.GetAssetPath(obj);
                        }
                    }
                }
            };
            subLists[index] = lists;
        }

        lists[0].DoList(rect);
        rect.y += lists[0].GetHeight();
        lists[1].DoList(rect);
    }

    private void SaveData()
    {
        PersistHelper.Serialize(Config, "external References.json");
    }

    private void OnGUI()
    {
        EditorGUI.BeginChangeCheck();
        scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true);
        serializedObject.Update();
        _reorderableList.DoLayoutList();
        serializedObject.ApplyModifiedProperties();
        GUILayout.EndScrollView();

        Config.Mode = EditorGUILayout.Popup("模式:", Config.Mode, new string[] { "动态全部添加 (新版)", "针对性添加 (旧版)" });
        Config.SearchPattern = EditorGUILayout.DelayedTextField("引用文件格式:", Config.SearchPattern);
        if (GUILayout.Button("执行!", GUILayout.Height(30)))
        {
            foreach (var projectPath in Config.ProjectPaths)
            {
                var xml = new XmlDocument();
                xml.Load(projectPath.path);
                if (Config.Mode == 0)
                    ChangeCsProjectNew(xml, projectPath, Config.SearchPattern, out _);
                else
                    ChangeCsProject(xml, projectPath, Config.SearchPattern);
                xml.Save(projectPath.path);
            }

            Debug.Log("更新完成!");
        }

        if (EditorGUI.EndChangeCheck())
            SaveData();
    }

    public static string ChangeCsProject(string genCsProjectPath, string content)
    {
        var config = PersistHelper.Deserialize<ExternalReferencesConfig>("external References.json");
        foreach (var csprojPath in config.ProjectPaths)
        {
            var name = Path.GetFileName(csprojPath.path);
            var name1 = Path.GetFileName(genCsProjectPath);
            if (name != name1)
                continue;
            var xml = new XmlDocument();
            xml.LoadXml(content);
            if (config.Mode == 0)
                return ChangeCsProjectNew(xml, csprojPath, config.SearchPattern, out _);
            else
                return ChangeCsProject(xml, csprojPath, config.SearchPattern);
        }

        return content;
    }

    private static string ChangeCsProject(XmlDocument xml, ProjectPathData csprojPath, string searchPattern)
    {
        XmlNodeList nodelist;
        var documentElement = xml.DocumentElement;
        var namespaceUri = xml.DocumentElement.NamespaceURI;
        if (!string.IsNullOrEmpty(namespaceUri))
        {
            var nsMgr = new XmlNamespaceManager(xml.NameTable);
            nsMgr.AddNamespace("ns", namespaceUri);
            nodelist = xml.SelectNodes("/ns:Project/ns:ItemGroup", nsMgr);
        }
        else nodelist = xml.SelectNodes("/Project/ItemGroup");

        foreach (var path in csprojPath.sharePaths)
        {
            var path1 = Path.GetFullPath(path.path);
            var dir = new DirectoryInfo(path1);
            var dirName = dir.Parent.FullName + Path.DirectorySeparatorChar;
            var files = Directory.GetFiles(path1, "*.*", SearchOption.AllDirectories);
            var patterns = searchPattern.Replace("*", "").Split('|');
            var fileList = new List<string>();
            var csPath = Path.GetFullPath(csprojPath.path);
            path1 = PathHelper.GetRelativePath(csPath, path1, true);
            dirName = PathHelper.GetRelativePath(csPath, dirName, true);
            foreach (var file in files)
            {
                var rPath = PathHelper.GetRelativePath(csPath, file, true);
                var isFilter = false;
                foreach (var filter in csprojPath.filters)
                {
                    var attributes = File.GetAttributes(filter);
                    var isDirectory = (attributes & FileAttributes.Directory) == FileAttributes.Directory;
                    if (isDirectory)
                    {
                        if (Path.GetDirectoryName(rPath) == filter)
                        {
                            isFilter = true;
                            break;
                        }
                    }
                    else if (rPath == filter)
                    {
                        isFilter = true;
                        break;
                    }
                }

                if (isFilter)
                    continue;
                foreach (var pattern in patterns)
                {
                    if (string.IsNullOrEmpty(pattern))
                        continue;
                    if (file.EndsWith(pattern))
                    {
                        fileList.Add(rPath);
                        break;
                    }
                }
            }

            files = fileList.ToArray();
            var extraPath = $"{csprojPath.extraPath}{Path.DirectorySeparatorChar}{path.extraPath}{Path.DirectorySeparatorChar}";
            while (extraPath.StartsWith(Path.DirectorySeparatorChar.ToString())) //兼容旧版本
                extraPath = extraPath.TrimStart(Path.DirectorySeparatorChar);
            var exist = CheckNodeContains(xml, nodelist, namespaceUri, dirName, path1, files, extraPath);
            if (!exist)
            {
                var node = CreateItemGroup(xml, namespaceUri, dirName, files, extraPath);
                documentElement.AppendChild(node);
            }
        }

        var settings = new XmlWriterSettings
        {
            Indent = true, // 启用缩进
            NewLineChars = "\n", // 设置换行符
            NewLineHandling = NewLineHandling.Replace,
            Encoding = System.Text.Encoding.UTF8,
            ConformanceLevel = ConformanceLevel.Document // 设置符合级别
        };
        using (var sw = new StringWriter())
        {
            using (var xmlWriter = XmlWriter.Create(sw, settings))
            {
                xml.WriteTo(xmlWriter);
            }

            return sw.ToString();
        }
    }

    public static string ChangeCsProjectNew(XmlDocument xml, ProjectPathData csprojPath, string searchPattern, out int existCount)
    {
        XmlNodeList nodelist;
        var documentElement = xml.DocumentElement;
        var namespaceUri = xml.DocumentElement.NamespaceURI;
        if (!string.IsNullOrEmpty(namespaceUri))
        {
            var nsMgr = new XmlNamespaceManager(xml.NameTable);
            nsMgr.AddNamespace("ns", namespaceUri);
            nodelist = xml.SelectNodes("/ns:Project/ns:ItemGroup", nsMgr);
        }
        else nodelist = xml.SelectNodes("/Project/ItemGroup");

        var csPath = Path.GetFullPath(csprojPath.path);
        
        existCount = 0;
        foreach (var sharePath in csprojPath.sharePaths)
        {
            var path = Path.GetFullPath(sharePath.path);
            var dir = new DirectoryInfo(path);
            path = PathHelper.GetRelativePath(csPath, path, true);
            var extraPath = $"{csprojPath.extraPath}{Path.DirectorySeparatorChar}{sharePath.extraPath}{Path.DirectorySeparatorChar}";
            while (extraPath.StartsWith(Path.DirectorySeparatorChar.ToString())) //兼容旧版本
                extraPath = extraPath.TrimStart(Path.DirectorySeparatorChar);
            var patterns = searchPattern.Split('|');
            var node = xml.CreateElement("ItemGroup", namespaceUri);
            int existNumber = 0;
            foreach (var pattern in patterns)
            {
                if (string.IsNullOrEmpty(pattern))
                    continue;
                var path2 = PathHelper.Combine(path, $"**\\{pattern}");
                var exist = CheckNodeContains(nodelist, path2, "Include", null);
                if (!exist)
                {
                    existNumber++;
                    existCount++;
                    CreateElement(xml, node, pattern.EndsWith(".cs") ? "Compile" : "Content", namespaceUri, path2, $"{extraPath}{dir.Name}\\%(RecursiveDir)%(FileName)%(Extension)");
                }
            }

            if (existNumber > 0)
                documentElement.AppendChild(node);
        }

        var filterNode = xml.CreateElement("ItemGroup", namespaceUri);
        int existNumber1 = 0;
        foreach (var filter in csprojPath.filters)
        {
            if (string.IsNullOrEmpty(filter))
                continue;
            var attributes = File.GetAttributes(filter);
            var isDirectory = (attributes & FileAttributes.Directory) == FileAttributes.Directory;
            var patterns = searchPattern.Split('|');
            var path = Path.GetFullPath(filter);
            path = PathHelper.GetRelativePath(csPath, path, true);
            if (isDirectory)
            {
                foreach (var pattern in patterns)
                {
                    if (string.IsNullOrEmpty(pattern))
                        continue;
                    var path2 = PathHelper.Combine(path, pattern);
                    var exist = CheckNodeContains(nodelist, path2, "Remove",null);
                    if (!exist)
                    {
                        existNumber1++;
                        var e = xml.CreateElement(pattern.EndsWith(".cs") ? "Compile" : "Content", namespaceUri);
                        e.SetAttribute("Remove", path2);
                        filterNode.AppendChild(e);
                    }
                }
            }
            else
            {
                var exist = CheckNodeContains(nodelist, path, "Remove", null);
                if (!exist)
                {
                    existNumber1++;
                    var e = xml.CreateElement(path.EndsWith(".cs") ? "Compile" : "Content", namespaceUri);
                    e.SetAttribute("Remove", path);
                    filterNode.AppendChild(e);
                }
            }
        }

        if (existNumber1 > 0)
            documentElement.AppendChild(filterNode);

        var settings = new XmlWriterSettings
        {
            Indent = true, // 启用缩进
            NewLineChars = "\n", // 设置换行符
            NewLineHandling = NewLineHandling.Replace,
            Encoding = System.Text.Encoding.UTF8,
            ConformanceLevel = ConformanceLevel.Document // 设置符合级别
        };
        using (var sw = new StringWriter())
        {
            using (var xmlWriter = XmlWriter.Create(sw, settings))
            {
                xml.WriteTo(xmlWriter);
            }

            return sw.ToString();
        }
    }

    private static bool CheckNodeContains(XmlDocument xml, XmlNodeList nodelist, string namespaceUri, string dirName, string path1, string[] files, string extraPath)
    {
        return CheckNodeContains(nodelist, path1, "Include", (node, nodeChild) =>
        {
            foreach (var file in files)
            {
                bool isExist = false;
                foreach (XmlNode childNode in nodeChild)
                {
                    if (childNode.LocalName != "Compile" & childNode.LocalName != "Content")
                        continue;
                    var attribute = childNode.Attributes.GetNamedItem("Include");
                    if (attribute == null)
                        continue;
                    var value = attribute.Value;
                    if (file == value) //必须一致性
                    {
                        isExist = true;
                        break;
                    }
                }

                if (!isExist)
                    CreateItemElement(xml, node, file, namespaceUri, dirName, extraPath);
            }

            foreach (XmlNode childNode in nodeChild) //检查移除的文件
            {
                if (childNode.LocalName != "Compile" & childNode.LocalName != "Content")
                    continue;
                var attribute = childNode.Attributes.GetNamedItem("Include");
                if (attribute == null)
                    continue;
                var value = attribute.Value;
                if (!files.Contains(value))
                    node.RemoveChild(childNode);
            }
        });
    }

    private static bool CheckNodeContains(XmlNodeList nodeList, string path, string attributeName = "Include", Action<XmlNode, XmlNodeList> action = null)
    {
        bool exist = false;
        for (int i = 0; i < nodeList.Count; i++)
        {
            var node = nodeList.Item(i);
            var nodeChild = node.ChildNodes;
            foreach (XmlNode childNode in nodeChild)
            {
                if (childNode.LocalName != "Compile" & childNode.LocalName != "Content")
                    continue;
                var attribute = childNode.Attributes.GetNamedItem(attributeName);
                if (attribute == null)
                    continue;
                var value = attribute.Value;
                if (value.Contains(path))
                {
                    exist = true;
                    break;
                }
            }

            if (exist)
            {
                action?.Invoke(node, nodeChild);
                break;
            }
        }

        return exist;
    }
    
    private static XmlNode CreateItemGroup(XmlDocument xml, string namespaceUri, string dirName, string[] files, string extraPath)
    {
        var node = xml.CreateElement("ItemGroup", namespaceUri);
        foreach (var file in files)
            CreateItemElement(xml, node, file, namespaceUri, dirName, extraPath);
        return node;
    }

    private static XmlNode CreateItemGroup(XmlDocument xml, string qualifiedName, string namespaceUri, string include, string link)
    {
        var node = xml.CreateElement("ItemGroup", namespaceUri);
        CreateElement(xml, node, qualifiedName, namespaceUri, include, link);
        return node;
    }

    private static void CreateItemElement(XmlDocument xml, XmlNode node, string file, string namespaceUri, string dirName, string extraPath)
    {
        if (file.EndsWith(".cs"))
        {
            var e = xml.CreateElement("Compile", namespaceUri);
            e.SetAttribute("Include", file);
            var innerText = file.Replace(dirName, "");
            if (file != innerText) //相同的目录就不需要link了
            {
                var e1 = xml.CreateElement("Link", namespaceUri);
                e1.InnerText = extraPath + innerText;
                e.AppendChild(e1);
            }

            node.AppendChild(e);
        }
        else
        {
            var e = xml.CreateElement("Content", namespaceUri);
            e.SetAttribute("Include", file);
            var innerText = file.Replace(dirName, "");
            if (file != innerText) //相同的目录就不需要link了
            {
                var e1 = xml.CreateElement("Link", namespaceUri);
                e1.InnerText = extraPath + innerText;
                e.AppendChild(e1);
            }

            node.AppendChild(e);
        }
    }

    private static void CreateElement(XmlDocument xml, XmlNode node, string qualifiedName, string namespaceUri, string include, string link)
    {
        var e = xml.CreateElement(qualifiedName, namespaceUri);
        e.SetAttribute("Include", include);
        var e1 = xml.CreateElement("Link", namespaceUri);
        e1.InnerText = link;
        e.AppendChild(e1);
        node.AppendChild(e);
    }
}
#endif